#include <stdio.h>
#include <vector>
#include <queue>

using namespace std;

#define posXAlice 0.0
#define posXBob 100000.0

// Definimos un Poligono.
struct Poligono {
	float puntoIzq; // la coodenada en el eje x del poligono que esta mas a la izquierda
	float puntoDer; // la coodenada en el eje x del poligono que esta mas a la derecha
	short altura;
	Poligono(){
		puntoIzq = 300000.0;
		puntoDer = -300000.0;
		altura = 0;
	}
} ;

// Definimos un Punto.
struct Punto{
	int x;
	int y;
	Punto() {x = 0; y = 0;}
	Punto(int xpos, int ypos) {x = xpos; y = ypos;}
};

// Clase para pasarle al heap para que la prioridad sea el minimo intervalo.
class Mayor{
public:
	int operator()(Poligono &P1,Poligono &P2){
		return ( P1.puntoDer > P2.puntoDer );
	}
};

// Clase para pasarle al heap para que la prioridad sea el maximo intervalo.
class Menor{
public:
	int operator()(Poligono &P1,Poligono &P2){
		return ( P1.puntoDer < P2.puntoDer );
	}
};

typedef priority_queue< Poligono, vector<Poligono>, Mayor> maxheap;
typedef priority_queue< Poligono, vector<Poligono>, Menor> minheap;

// Funcion que determina la interseccion con el eje x de un segmento si es que la hay.
// Si no cruza al eje x devuelvo 0.0 porque se que ningun segmento puede pasar por este punto
float cruceConEjeX(Punto p1, Punto p2){
	// me fijo si cruza el eje x
	if ((p1.y < 0 && p2.y >= 0) || (p2.y < 0 && p1.y >= 0) ) {
		return (-p1.y) * (p2.x - p1.x) / (p2.y- p1.y)+ p1.x;
	} else {
		return 0.0;
	}
}

// Funcion que determina si un poligono encierra a Bob y a Alice.
Poligono procesarPoligono(short height, vector<Punto> &vertices, bool &incluyeAlice, bool &incluyeBob){
	int cantVertices = vertices.size();
	Poligono poligono = Poligono();
	poligono.altura = height;
	int crucesIzquierdaAlice = 0, crucesIzquierdaBob = 0;
	for (int i = 0; i < cantVertices-1; ++i){
		float cruceX = cruceConEjeX(vertices[i], vertices[i+1]);
		if (cruceX != 0.0){
			if (cruceX < posXAlice) crucesIzquierdaAlice++;
			if (cruceX < posXBob) crucesIzquierdaBob++;			
			if (cruceX < poligono.puntoIzq ) poligono.puntoIzq = cruceX;
			if (cruceX > poligono.puntoDer ) poligono.puntoDer = cruceX;
		}
	}
	float cruceX = cruceConEjeX(vertices[cantVertices-1], vertices[0]);
	if (cruceX != 0.0){
		if (cruceX < posXAlice) crucesIzquierdaAlice++;
		if (cruceX < posXBob) crucesIzquierdaBob++;			
		if (cruceX < poligono.puntoIzq ) poligono.puntoIzq = cruceX;
		if (cruceX > poligono.puntoDer ) poligono.puntoDer = cruceX;
	}

	incluyeAlice = (crucesIzquierdaAlice % 2 == 1) ;
	incluyeBob = (crucesIzquierdaBob % 2 == 1);
	 
	return poligono;
}

// Funcion que cuenta la cantidad de ascensos y descensos saliendo de Alice, ademas devuelve la altura final de Alice.
short asciendoYdesciendoAlice(maxheap &encierranAlice, int &ascendido, int &descendido){
    
	// No hay poligonos que encierran a Alice y por ende no hay altura final de Alice
	if (encierranAlice.empty()) return 0;
	
	short alturaFinalAlice;
    short alturaActual = encierranAlice.top().altura;
    if(encierranAlice.size() == 1){
		// Me quedo con la ultima altura de Alice, 
		// y considero este caso en que Alice solo tenga 1 solo poligono que lo encierre
		alturaFinalAlice = alturaActual;
    }
    encierranAlice.pop();
	short alturaSiguiente; 
	while (encierranAlice.size() > 0){
		alturaSiguiente = encierranAlice.top().altura; 
		if (alturaSiguiente >= alturaActual){
			ascendido+= alturaSiguiente - alturaActual;    
	    } else{
			descendido+= alturaActual - alturaSiguiente;
	    }
	    alturaActual = alturaSiguiente;
	    // Me quedo con la ultima altura de Alice 
		alturaFinalAlice = alturaActual;
	    encierranAlice.pop();
    }
	return alturaFinalAlice;
}
// Funcion que cuenta la cantidad de ascensos y descensos llegando hacia Bob.
void asciendoYdesciendoBob(minheap &encierranBob, int &ascendido, int &descendido, short alturaFinalAlice){

	if (!encierranBob.empty()){
		short alturaActual; 
		// Si ya tenia altura desde Alice la asigno, sino uso la altura mas externa de Bob
		if(alturaFinalAlice == 0){
			alturaActual = encierranBob.top().altura; 
			encierranBob.pop();
		} else{
			alturaActual = alturaFinalAlice;
		}
		short alturaSiguiente;
		while (encierranBob.size() > 0){
			alturaSiguiente = encierranBob.top().altura;
			if (alturaSiguiente >= alturaActual){
				ascendido+= alturaSiguiente - alturaActual;    
			} else{
				descendido+= alturaActual - alturaSiguiente;
			}
			alturaActual = alturaSiguiente;
			encierranBob.pop();
		}
	}
}

int main() {
	maxheap encierranAlice;
	minheap encierranBob;  
	int ascendido;
	int descendido;
	short alturaFinalAlice;
	short cantTestCases;
	scanf ("%hd\n",&cantTestCases);

	for (short testcase = 0; testcase < cantTestCases; ++testcase){
		ascendido = 0;
		descendido = 0;
		alturaFinalAlice = 0;
		short cantHeightCurves;
		scanf ("%hd\n",&cantHeightCurves);

		for (short heightcurve = 0; heightcurve < cantHeightCurves; ++heightcurve){
			short height;
			scanf ("%hd ",&height);

			short cantVertices;
			scanf ("%hd ",&cantVertices);

			vector<Punto> vertices(cantVertices);
			int x, y;
			for (short vertice = 0; vertice < cantVertices; ++vertice){
				scanf ("%d %d ",&x, &y);
				vertices[vertice] = Punto(x, y);
			}
			
			bool incluyeAlice, incluyeBob;
			Poligono poligono = procesarPoligono(height, vertices, incluyeAlice, incluyeBob);

			if(incluyeAlice && !incluyeBob) encierranAlice.push(poligono);
			if(incluyeBob && !incluyeAlice) encierranBob.push(poligono);
		}
		
		alturaFinalAlice = asciendoYdesciendoAlice(encierranAlice, ascendido, descendido);
		asciendoYdesciendoBob(encierranBob, ascendido, descendido, alturaFinalAlice);
    
		printf("%d %d\n", ascendido, descendido); 
	}
	return 0;
}
